#include "CBL_Cpu.h"

#include <csignal>
#include <cstring>
#include <iostream>
#include <time.h>

#include "CBL_Debug.h"
#include "CBL_Fpu.h"

#ifdef CB_WINDOWS
#include <windows.h>
#include <float.h>
#else
#include <unistd.h>
#endif

CB_SUBNAMESPACE_START(Cpu)

// ---------------------------------------------------------------------------------------------
//  Cross platform Functions
// ---------------------------------------------------------------------------------------------

static inline uint32 GetNumPhysicalCpus()
{
#ifdef CB_WINDOWS
	// Windows query
    SYSTEM_INFO Info;
    GetSystemInfo(&Info);
    return Info.dwNumberOfProcessors;
#elif defined(_SC_NPROCESSORS_ONLN)
	// Unix query
    return sysconf(_SC_NPROCESSORS_ONLN);
#else
	return 1; // Fallback
#endif
}

// ---------------------------------------------------------------------------------------------

void Wait(uint32 _NumMs)
{
	if (_NumMs > 0)
	{
#ifdef CB_WINDOWS		
		SleepEx(_NumMs, false);
#else
		timespec Time = { 0, _NumMs * 100 };
    	while (nanosleep(&Time, &Time) != 0) 
			if (Time.tv_nsec < 0) return;	
#endif
	}
}

// ---------------------------------------------------------------------------------------------

void Sleep(uint32 _NumMs)
{
	if (_NumMs > 0)
	{
#ifdef CB_WINDOWS
		SleepEx(_NumMs, true);
#else
		timespec Time = { 0, _NumMs * 100 };
    	nanosleep(&Time, NULL);
#endif
	}
}

// ---------------------------------------------------------------------------------------------

double GetTimeMs()
{
	clock_t Time = clock();
	return Time * (1000.0 / CLOCKS_PER_SEC);
}

// ---------------------------------------------------------------------------------------------
//  Cached Data
// ---------------------------------------------------------------------------------------------

	static SCpuInfo g_Cpu;

// ---------------------------------------------------------------------------------------------


#ifdef CB_ARCH_PPC

	// ---------------------------------------------------------------------------------------------
	//  PowerPC utility functions
	// ---------------------------------------------------------------------------------------------

	static void SigIllHandler (int CB_UNUSED(_Signal))
	{
		g_Cpu.SIMD = NoSIMD;
	}

	// ---------------------------------------------------------------------------------------------
	//  implementation
	// ---------------------------------------------------------------------------------------------

	const SCpuInfo& GetCpuInfo()
	{
		if (g_Cpu.Processors == 0)
		{
			g_Cpu.Type = PowerPC;
			g_Cpu.Processors = GetNumPhysicalCpus();
			g_Cpu.Cores = 1;		// TODO?
			g_Cpu.LogicalUnits = 1; // TODO?
			
			g_Cpu.SIMD = Altivec;
			
			void (*pPrevHandler)(int) = signal(SIGILL, SigIllHandler);
			Debug::Assert(pPrevHandler != SIG_ERR);
			
			asm volatile (".long 0x10000484");
			
			signal(SIGILL, pPrevHandler);
		}
		
		return g_Cpu;
	}

    // ---------------------------------------------------------------------------------------------

	namespace x86
	{
		void FlushMMXRegister() {}
		void SetFastFPU() {}
		void SetPreciseFPU() {}
		void EnableCRTSSE() {}
		void DisableCRTSSE() {}
	}

#elif defined(CB_ARCH_X86)

	#include <mmintrin.h>

	// ---------------------------------------------------------------------------------------------
	//  x86 Data
	// ---------------------------------------------------------------------------------------------

	enum X86Registers	{ RegEAX=0, RegEBX, RegEDX, RegECX }; // EDX and ECX swapped because of manufacturer string
	enum FPUState		{ FPUUnknown=0, FPUSingle, FPUDouble };
    
	// ---------------------------------------------------------------------------------------------
	//  x86 utility functions
	// ---------------------------------------------------------------------------------------------

	static inline bool IsCpuIDSupported()
	{
		bool Check = false;
		CB_ASM 
		{
			CB_ASM_pushfl;		// push EFLAGS to stack
			pop eax;			// EFLAGS -> eax
			mov ebx, eax;		// eax -> ebx (for compare)
			xor eax, 0x200000;	// modify cpuid bit
			push eax;			// push eax to stack
			CB_ASM_popfl;		// commit EFLAGS
			CB_ASM_pushfl;		// push EFLAGS to stack again
			pop eax;			// EFLAGS -> eax
			cmp eax, ebx;		// compare original and modified EFLAGS
			jz no_cpuid;
			mov Check, 1;		// if modification was kept -> supported
		no_cpuid:
		}
			
		return Check;
	}

	// ---------------------------------------------------------------------------------------------

	static inline void QueryCpuID(uint32 _EAX, uint32 _EBX, uint32 _ECX, uint32 _EDX, uint32* _pRegisters)
	{
		uint32 OutEAX = 0;
		uint32 OutEBX = 0;
		uint32 OutECX = 0;
		uint32 OutEDX = 0;
		
		if (IsCpuIDSupported())
		{
			CB_ASM
			{
				mov eax, _EAX;		// setting parameters for cpuid
				mov ebx, _EBX;
				mov ecx, _ECX;
				mov edx, _EDX;
				cpuid;				// get infos
				mov OutEAX, eax;    // move infos to array
				mov OutEBX, ebx;
				mov OutECX, ecx;
				mov OutEDX, edx;				
			}
		}
		
		_pRegisters[RegEAX] = OutEAX;
		_pRegisters[RegEBX] = OutEBX;
		_pRegisters[RegECX] = OutECX;
		_pRegisters[RegEDX] = OutEDX;
	}

	// ---------------------------------------------------------------------------------------------

	static inline bool IsCpuIDBitSet(uint32 _EAX, uint32 _EBX, uint32 _ECX, uint32 _EDX, uint32 _Bit, X86Registers _Register)
	{
		uint32 Registers[4];
		QueryCpuID(_EAX,_EBX,_ECX,_EDX, Registers);
		
		return (Registers[_Register] & (1 << _Bit)) != 0;
	}

	// ---------------------------------------------------------------------------------------------

	static inline bool IsAMDExtraFunctionSupported(uint32 _FunctionMask)
	{
		uint32 RegistersExtra[4];
		QueryCpuID(0x80000000,0,0,0, RegistersExtra); // check for extended functions
		
		return (RegistersExtra[RegEAX] >= _FunctionMask);
	}

	// ---------------------------------------------------------------------------------------------

	static inline uint16 GetFPUState()
	{
		return Fpu::GetFlags();
	}

	// ---------------------------------------------------------------------------------------------

	static inline void SetFPUState(uint16 _State)
	{
		Fpu::SetFlags(_State);
	}

	// ---------------------------------------------------------------------------------------------
	//  implementation
	// ---------------------------------------------------------------------------------------------

	const SCpuInfo& GetCpuInfo()
	{
		if (g_Cpu.Processors == 0)
		{
			g_Cpu.Processors = GetNumPhysicalCpus();
			g_Cpu.Cores = 1;
			g_Cpu.LogicalUnits = 1;
			g_Cpu.SIMD = NoSIMD;
			
			uint32 RegistersEAX0[4];
            QueryCpuID(0,0,0,0, RegistersEAX0);
			
            char *Manufacturer = reinterpret_cast<char*>(&RegistersEAX0[RegEBX]);
            if (strncmp(Manufacturer, "GenuineIntel", 12) == 0)
                g_Cpu.Type = Intel;
			
            if (strncmp(Manufacturer, "AuthenticAMD", 12) == 0)
                g_Cpu.Type = AMD;
			
			// Get hyper-threading count
			uint32 RegistersEAX1[4];
			QueryCpuID(1,0,0,0, RegistersEAX1);
			
			if ((RegistersEAX1[RegEDX] & (1 << 28)) != 0)
			{   
				uint32 HTBitMask = 0x00FF0000;
				g_Cpu.LogicalUnits = (RegistersEAX1[RegEBX] & HTBitMask) >> 16;
			}
			
			// Get multi-core count
			switch (g_Cpu.Type)
			{
			case Intel:
				if (RegistersEAX0[RegEAX] >= 4)
				{
					uint32 RegistersEAX4[4];
					QueryCpuID(4,0,0,0, RegistersEAX4);
					g_Cpu.Cores = (RegistersEAX4[RegEAX] >> 26) +1;
				}
				break;
					
			case AMD:
				if (IsAMDExtraFunctionSupported(0x80000008))
				{
					uint32 RegistersExtra[4];
					QueryCpuID(0x80000008,0,0,0, RegistersExtra); // check multi-core values
					
					g_Cpu.Cores = (RegistersExtra[RegECX] & 0x0000007F) +1;
				}
				break;
					
			default:
				break;
			}
			
			// Check SIMD support
			if (IsCpuIDBitSet(1,0,0,0, 19, RegECX))         // SSE4.1 Bit
				g_Cpu.SIMD = SSE41;
			else if (IsCpuIDBitSet(1,0,0,0, 19, RegECX))    // SSE4.1 Bit
				g_Cpu.SIMD = SSE41;
			else if (IsCpuIDBitSet(1,0,0,0, 9, RegECX))     // SSE3 Bit
				g_Cpu.SIMD = SSE3;
			else if (IsCpuIDBitSet(1,0,0,0, 26, RegEDX))    // SSE2 Bit
				g_Cpu.SIMD = SSE2;
			else if (IsCpuIDBitSet(1,0,0,0, 25, RegEDX))    // SSE Bit
				g_Cpu.SIMD = SSE;
			else
			{
				switch (g_Cpu.Type)
				{
				case Intel:
					if (IsCpuIDBitSet(1,0,0,0, 23, RegEDX)) // MMX Bit
						g_Cpu.SIMD = MMX;
					break;
					
				case AMD:
					if (IsAMDExtraFunctionSupported(0x80000001))
					{
						if (IsCpuIDBitSet(0x80000001,0,0,0, 31, RegEDX)) // 3DNow! Bit
							g_Cpu.SIMD = MMX;                                   
					}                
					break;
					
				default:
						g_Cpu.SIMD = NoSIMD;
					break;
				}
			}
			
			g_Cpu.Processors /= g_Cpu.Cores;
		}
		
		return g_Cpu;
	}	
	
	// ---------------------------------------------------------------------------------------------
	//	x86 specific functions
	// ---------------------------------------------------------------------------------------------

	namespace x86
	{
		void FlushMMXRegister()
		{
			_mm_empty();
		}
		
		// ---------------------------------------------------------------------------------------------
		
		void SetFastFPU()
		{
			uint32 CurrentState = GetFPUState();
			
			if (CurrentState & Fpu::ExtendedPrecision)
			{
				SetFPUState(CurrentState & ~Fpu::ExtendedPrecision);
			}
			CurrentState = GetFPUState();
		}
		
		// ---------------------------------------------------------------------------------------------
		
		void SetPreciseFPU()
		{
			uint16 CurrentState = GetFPUState();
			
			if ((CurrentState & Fpu::ExtendedPrecision) != Fpu::ExtendedPrecision)
			{
				SetFPUState(CurrentState | Fpu::ExtendedPrecision);
			}			
			CurrentState = GetFPUState();
		}
		
		// ---------------------------------------------------------------------------------------------
		
		void EnableCRTSSE() {}
		
		// -------------------------------------------------------------------------------------------------
		
		void DisableCRTSSE() {}
	}

#else

	// ---------------------------------------------------------------------------------------------
	//  Unknown Architecture
	// ---------------------------------------------------------------------------------------------

	const SCpuInfo& GetCpuInfo()
	{
		if (g_Cpu.Processors == 0)
		{
			g_Cpu.Type = CpuTypeUnknown;
			g_Cpu.Processors = GetNumPhysicalCpus();
			g_Cpu.Cores = 1;
			g_Cpu.LogicalUnits = 1;
			
			g_Cpu.SIMD = NoSIMD;
		}
		
		return g_Cpu;
	}

	// ---------------------------------------------------------------------------------------------

	namespace x86
	{
		void FlushMMXRegister() {}
		void SetFastFPU() {}
		void SetPreciseFPU() {}
		void EnableCRTSSE() {}
		void DisableCRTSSE() {}
	}

	// ---------------------------------------------------------------------------------------------

#endif

CB_SUBNAMESPACE_END